%%%-------------------------------------------------------------------
%%% File:      erlymate.erl
%%% @author    Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
%%% @copyright 2007 Roberto Saccon
%%% @doc  
%%% ErlyMate server application
%%% @end  
%%%
%%% parts of erlymate.erl are derived from erlaunch.erl source code.
%%% Original erlaunch.erl license:
%%%-------------------------------------------------------------------
%%% erlaunch - execute selected Erlang functions from system command line
%%% Copyright (c)2003 Cat's Eye Technologies.  All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%%   Redistributions of source code must retain the above copyright
%%%   notice, this list of conditions and the following disclaimer.
%%%
%%%   Redistributions in binary form must reproduce the above copyright
%%%   notice, this list of conditions and the following disclaimer in
%%%   the documentation and/or other materials provided with the
%%%   distribution.
%%%
%%%   Neither the name of Cat's Eye Technologies nor the names of its
%%%   contributors may be used to endorse or promote products derived
%%%   from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF AsDVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%%%-------------------------------------------------------------------
%%%
%%% the following license applies to all parts not covered by the above license:
%%%
%%% The MIT License
%%%
%%% Copyright (c) 2007 Roberto Saccon
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%
%%% @since 2007-12-02 by Roberto Saccon
%%%-------------------------------------------------------------------
-module(erlymate).
-author('rsaccon@gmail.com').


%% API
-export([server/1, fwrite/3]).

-define(ERLYMATE_VERSION, '0.1').
-define(TCP_OPTS, [list, 
    {reuseaddr, true}, 
    {nodelay, true}, 
    {active, false}, 
    {packet, 4}]).


%%====================================================================
%% API
%%====================================================================
%% @spec () -> any()
%% @doc 
%% Starts the server
%% @end 
%%--------------------------------------------------------------------
server([Version, Port, Cookie]) ->
    case list_to_atom(Version) of
        ?ERLYMATE_VERSION ->
            server(Port, Cookie);
        _ ->
            init:stop()
    end.


%%--------------------------------------------------------------------
%% @spec  
%% @doc
%% @end 
%%--------------------------------------------------------------------
fwrite(Socket, Format, Args) ->
    gen_tcp:send(Socket, [0, io_lib:format(Format, Args)]),
    ok.
     
   
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
server(Port, Cookie) ->
    {ok, ListenSocket} = gen_tcp:listen(list_to_integer(Port), ?TCP_OPTS),
    loop(ListenSocket, Cookie).


loop(ListenSocket, Cookie) ->
    {ok, Socket} = gen_tcp:accept(ListenSocket),
    Pid = spawn(fun() -> handle(Socket, Cookie) end),
    gen_tcp:controlling_process(Socket, Pid),
    Pid ! continue,
    loop(ListenSocket, Cookie).


handle(Socket, Cookie) ->
    receive
        continue ->
            case catch handle0(Socket, Cookie) of
                {'EXIT', Reason} ->
                    gen_tcp:send(Socket, [255, io_lib:format("~p", [Reason])]);
                _ ->
                    ok
            end,
            gen_tcp:close(Socket)
    end.   
    

handle0(Socket, Cookie) ->
    {ok, Cookie} = read_string(Socket),     
    {ok, Command} = read_string(Socket),
    handle1(Socket, list_to_atom(Command)).   
    
    
handle1(Socket, stop) ->
    gen_tcp:close(Socket),
    init:stop();      
handle1(Socket, reload) ->
    gen_tcp:send(Socket, [1]),  
    code:purge(erlymate_compile),
    code:load_file(erlymate_compile),
    code:purge(erlymate_help),
    code:load_file(erlymate_help), 
    code:purge(erlymate_shell),
    code:load_file(erlymate_shell),
    {exit, 1};  
handle1(Socket, Cmd) ->
    Module = module(Cmd),
    case read_string(Socket) of
        {ok, TermString} ->
            case erl_scan:string(TermString) of
                {ok,Tokens,_} ->
                    case erl_parse:parse_term(Tokens) of 
                        {ok,Term} ->
                            handle1(Socket, Module, Term);
                        Err ->
                            io:format("TRACE ~p:~p Error:~n~p~n",[?MODULE, ?LINE, Err]),
                            gen_tcp:send(Socket, [1]),
                            invalid
                    end;        
                _ ->
                    gen_tcp:send(Socket, [1]),
                    invalid
            end; 
        _ ->
            gen_tcp:send(Socket, [1]),
            invalid
    end.
    
    
handle1(Socket, Module, Term) ->    
    case catch Module:ok() of
        ok ->   
            gen_tcp:send(Socket, [0]),                      
            Args = read_list(Socket),
            case catch apply(Module, run, [Socket, Term | Args]) of
                 {exit, N} ->
                     gen_tcp:send(Socket, [N + 1]),
                     {exit, N};
                 _ ->
                     gen_tcp:send(Socket, [1]),
                     invalid_module
            end; 
        _ ->
            gen_tcp:send(Socket, [1]),
            not_found
    end.
    
         
read_string(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, [0 | String]} ->
            {ok, String};
        {ok, [EOF | _String]} when EOF > 0 ->
            {error, eof};
        {error, Reason} ->
            {error, Reason}
    end.
 
 
read_list(Socket) ->
    read_list(Socket, []).
read_list(Socket, Acc) ->
    case read_string(Socket) of
        {ok, ""} ->
            lists:reverse(Acc);
        {ok, Packet} ->
            read_list(Socket, [Packet | Acc])
    end.
    
          
module(Cmd) ->
    list_to_atom("erlymate_" ++ atom_to_list(Cmd)).